package com.koc.robot.font;

import android.opengl.GLES11;
import com.koc.robot.Filter;
import com.koc.robot.FilterMode;
import com.koc.robot.Global;
import com.koc.robot.GraphicsHelper;
import com.koc.robot.freetype.Library;
import com.koc.robot.freetype.FreeType;
import com.koc.robot.util.FileHandle;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import com.koc.robot.freetype.Face;
import com.koc.robot.freetype.FreeType;

/**
 * A TrueType font implementation on Android platform
 * TODO: destructor
 */
public class TrueTypeFont
{
	class Glyph
	{
		//public int list = 0;
		public int texture = 0;
		public int spacing = 0;
		public int w = 0;
		public int h = 0;
		
		public int bearingX = 0;
		public int bearingY = 0;
		
		// render buffer
		public FloatBuffer vertexbuffer = null;
		public FloatBuffer texturebuffer = null;
	}
	
	private int height;
	private float lineHeight;
	private float mSpacing; // modifies the spacing by multiplying it with this value
	
	private ArrayList<Integer> textures = null;
	private HashMap<Integer, Glyph> glyphs = null; 
	private Filter filter;
	
	private static final int TEXTURE_WIDTH = 512;
	private static final int TEXTURE_HEIGHT = 512;
	private static final int TEXTURE_PADDING = 1;
	
	private int texture_x, texture_y;
	private int rowHeight;
	
	private static Library freetypelib = null;
	
	// freetype related logics
	private Face face;
	
	public static void initFreeType()
	{
		freetypelib = FreeType.initFreeType();
	}
	
	public static void releaseFreeType()
	{
		FreeType.doneFreeType(freetypelib);
	}
	
	public void load(String fontfile, int fontSize) throws Exception
	{
		FileHandle handle = new FileHandle(fontfile);
		byte[] fontData = handle.readBytes();
		
		face = FreeType.newMemoryFace(freetypelib, fontData, fontData.length, 0);
		
		textures = new ArrayList<Integer>();
		glyphs = new HashMap<Integer, Glyph>();
		filter = new Filter();
		
		createTexture();
		
		glyphs = new HashMap<Integer, Glyph>(); 
		
		FreeType.setPixelSizes(face, fontSize, fontSize);
		
		height = face.getSize().getMetrics().getHeight() >> 6;
		
		lineHeight = 1;
		mSpacing = 1;
	}
	
	public float getHeight() 
	{
		return height;
	}
	
	public float getWidth(String line)
	{
		if (line.length() == 0) return 0;
		float temp = 0;
		
		Glyph g;
		
		int lineLen = line.length();
		for (int i = 0; i < lineLen; i++)
		{
			int c = line.charAt(i);
			g = glyphs.get(Integer.valueOf(c));
			if (g == null)
				g = addGlyph(c);
			temp += g.spacing * mSpacing;
		}
		
		return temp;
	}
	
	public float getLineHeight()
	{
		return lineHeight;
	}
	
	private Glyph addGlyph(int glyph)
	{
		// first get GlyphData
		GlyphData data = null;
		try
		{
			data = GlyphData.getGlyphData(glyph, face);
		}
		catch (Exception e)
		{
			// eat it for now
		}
		int w = data.getWidth();
		int h = data.getHeight();
		
		if (texture_x + w + TEXTURE_PADDING > TEXTURE_WIDTH)
		{
			texture_x = TEXTURE_PADDING;
			texture_y += rowHeight;
			rowHeight = TEXTURE_PADDING;
		}
		if (texture_y + h + TEXTURE_PADDING > TEXTURE_HEIGHT)
		{
			createTexture();
		}
		
		Glyph g = new Glyph();
		g.texture = 0;
		g.spacing = data.getAdvance();
		
		g.w = w;
		g.h = h;
		g.bearingX = data.getBearingX();
		g.bearingY = data.getBearingY();
		
		if (w > 0 && h > 0)
		{
			g.texture = textures.get(textures.size() - 1);
			GraphicsHelper.bindTexture(g.texture);
			GLES11.glTexSubImage2D(GLES11.GL_TEXTURE_2D, 0, texture_x, texture_y, w, h, GLES11.GL_LUMINANCE_ALPHA, GLES11.GL_UNSIGNED_BYTE, ByteBuffer.wrap(data.getData()));
			
			// create the vertex buffer and texture coordinates buffers
			float[] coords = 
			{
				0, 0,
				0, h,
				w, h,
				w, 0
			};
			ByteBuffer buffer = ByteBuffer.allocateDirect(8 * 4);
			buffer.order(ByteOrder.nativeOrder());
			g.vertexbuffer = buffer.asFloatBuffer();
			for (int i = 0; i < 8; i++)
			{
				g.vertexbuffer.put(coords[i]);
			}
			g.vertexbuffer.position(0);
			
			// now the texturebuffer
			float[] texcoords = 
			{
				(float)texture_x / TEXTURE_WIDTH, (float)texture_y / TEXTURE_HEIGHT,
				(float)texture_x / TEXTURE_WIDTH, (float)(texture_y + h) / TEXTURE_HEIGHT,
				(float)(texture_x + w) / TEXTURE_WIDTH, (float)(texture_y + h) / TEXTURE_HEIGHT,
				(float)(texture_x + w) / TEXTURE_WIDTH, (float)texture_y / TEXTURE_HEIGHT
			};
			buffer = ByteBuffer.allocateDirect(8 * 4);
			buffer.order(ByteOrder.nativeOrder());
			g.texturebuffer = buffer.asFloatBuffer();
			for (int i = 0; i < 8; i++)
			{
				g.texturebuffer.put(texcoords[i]);
			}
			g.texturebuffer.position(0);
		}
		
		if (w > 0)
			texture_x += (w + TEXTURE_PADDING);
		if (h > 0)
			rowHeight = Math.max(rowHeight, h + TEXTURE_PADDING);
		
		glyphs.put(Integer.valueOf(glyph), g);
		return g;
	}
	
	public void print(String text, float x, float y)
	{
		float dx = 0.0f; // spacing counter for newline handling
		
		GLES11.glPushMatrix();
		
		GLES11.glTranslatef(x, y, 0);
		{
			// iterator through each character and draw it
			int strLen = text.length();
			for (int i = 0; i < strLen; i++)
			{ 
				int g = text.charAt(i);
				if (g == '\n')
				{
					// wrap newline, but do not print it
					GLES11.glTranslatef(-dx, (float)Math.floor(getHeight() * getLineHeight() + 0.5f), 0);
					dx = 0.0f;
					continue;
				}
				
				Glyph glyph = glyphs.get(Integer.valueOf(g));
				if (glyph == null)
					glyph = addGlyph(g);
				// now we began to render the text
				GLES11.glPushMatrix();
				GLES11.glTranslatef(0, (float) Math.floor(getHeight() / 1.25f + 0.5f), 0);
				GraphicsHelper.bindTexture(glyph.texture);
				if (glyph.w > 0 && glyph.h > 0)
				{
					GLES11.glEnableClientState(GLES11.GL_VERTEX_ARRAY);
					GLES11.glEnableClientState(GLES11.GL_TEXTURE_COORD_ARRAY);
					GLES11.glVertexPointer(2, GLES11.GL_FLOAT, 0, glyph.vertexbuffer);
					GLES11.glTexCoordPointer(2, GLES11.GL_FLOAT, 0, glyph.texturebuffer);
					
					GLES11.glPushMatrix();
					GLES11.glTranslatef(glyph.bearingX, -glyph.bearingY, 0.0f);
					GLES11.glDrawArrays(GLES11.GL_TRIANGLE_FAN, 0, 4);
					GLES11.glPopMatrix();
					
					GLES11.glDisableClientState(GLES11.GL_TEXTURE_COORD_ARRAY);
					GLES11.glDisableClientState(GLES11.GL_VERTEX_ARRAY);
				}
				GLES11.glPopMatrix();
				GLES11.glTranslatef(glyph.spacing, 0, 0);
				dx += glyph.spacing;
			}
		}
		GLES11.glPopMatrix();
	}
	
	private void createTexture()
	{
		texture_x = texture_y = rowHeight = TEXTURE_PADDING;
		int t[] = new int[1];
		GLES11.glGenTextures(1, t, 0);
		textures.add(Integer.valueOf(t[0]));
		GraphicsHelper.bindTexture(t[0]);
		
		GLES11.glTexParameteri(GLES11.GL_TEXTURE_2D, GLES11.GL_TEXTURE_MAG_FILTER, 
				(filter.mag == FilterMode.FILTER_LINEAR) ? GLES11.GL_LINEAR : GLES11.GL_NEAREST);
		GLES11.glTexParameteri(GLES11.GL_TEXTURE_2D, GLES11.GL_TEXTURE_MIN_FILTER,
				(filter.min == FilterMode.FILTER_LINEAR) ? GLES11.GL_LINEAR : GLES11.GL_NEAREST);
		GLES11.glTexParameterf(GLES11.GL_TEXTURE_2D, GLES11.GL_TEXTURE_WRAP_S, GLES11.GL_CLAMP_TO_EDGE);
		GLES11.glTexParameterf(GLES11.GL_TEXTURE_2D, GLES11.GL_TEXTURE_WRAP_T, GLES11.GL_CLAMP_TO_EDGE);
		
		// because we paused from bitmap (actually, true type font should use GL_LUMINANCE_ALPHA format)
		int format = GLES11.GL_LUMINANCE_ALPHA;
		
		// Initialize the texture
		GLES11.glTexImage2D(
				GLES11.GL_TEXTURE_2D,
				0,
				GLES11.GL_LUMINANCE_ALPHA,
				TEXTURE_WIDTH,
				TEXTURE_HEIGHT,
				0,
				format,
				GLES11.GL_UNSIGNED_BYTE,
				null
				);
		
		byte[] emptyDataArr = new byte[TEXTURE_WIDTH * TEXTURE_HEIGHT * 4];
		Arrays.fill(emptyDataArr, (byte) 0);
		ByteBuffer emptyData = ByteBuffer.wrap(emptyDataArr);
		
		// Fill the texture with transparent black
		GLES11.glTexSubImage2D(GLES11.GL_TEXTURE_2D,
					0,
					0,
					0,
					TEXTURE_WIDTH,
					TEXTURE_HEIGHT,
					format,
					GLES11.GL_UNSIGNED_BYTE,
					emptyData); 
	}
}
